﻿using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EntityFrameworkCore.MemoryJoin;
using Microsoft.EntityFrameworkCore;

namespace Despatching.Demo
{
    class Program
    {
        static int _expiredCount = 0;
        static int _executeCount = 0;

        static readonly Despatcher<IdPair> _despatcher = new Despatcher<IdPair>(new DespatcherOptions { ExpireIn = TimeSpan.FromMilliseconds(100) });

        static Task<IdPair?> GetAsync()
        {
            var db = new DemoDbContext();
            var qy = from s in db.Sources
                     where !db.Results.Any(r => s.Id1 == r.Id1 && s.Id2 == r.Id2)
                     select new IdPair { Id1 = s.Id1, Id2 = s.Id2 };

            return _despatcher.GetAsync(async executings =>
            {
                var es = db.FromLocalList(executings.ToList());
                var qy2 = from s in qy
                          where !es.Any(r => s.Id1 == r.Id1 && s.Id2 == r.Id2)
                          select new IdPair { Id1 = s.Id1, Id2 = s.Id2 };
                return await qy2.Take(50).ToListAsync();
            });
        }

        static Task<bool> BackAsync(IdPair idPair)
        {
            var db = new DemoDbContext();
            return _despatcher.BackAsync(idPair, it =>
            {
                db.Results.Add(new Result { Id1 = it.Id1, Id2 = it.Id2 });
                return db.SaveChangesAsync();
            });
        }

        static async Task Main(string[] args)
        {
            var db = new DemoDbContext();
            db.Database.EnsureDeleted();
            db.Database.EnsureCreated();
            db.Database.Migrate();

            var src = from id1 in Enumerable.Range(1, 100)
                      from id2 in Enumerable.Range(1, 100)
                      select new Source { Id1 = id1, Id2 = id2 };

            db.Sources.AddRange(src.ToArray());
            await db.SaveChangesAsync();

            var random = new Random();
            // 创建10个线程模拟客户端
            var tasks = Enumerable.Range(1, 10).Select(_ => Task.Run(async () =>
            {
                while(true)
                {
                    // 客户端请求一个id
                    var id = await GetAsync();
                    if(id == null) // 代表所有任务结束
                        return;
                    // 随机的执行时间
                    var time = random.Next(10, 130);
                    await Task.Delay(time);
                    Console.WriteLine(id);
                    Interlocked.Increment(ref _executeCount);

                    // 执行完反馈
                    var isSaved = await BackAsync(id);
                    if(!isSaved)
                        Interlocked.Increment(ref _expiredCount);
                };
            }));

            await Task.WhenAll(tasks);
            Console.WriteLine("执行结束");
            Console.WriteLine($"执行次数:{_executeCount}");
            Console.WriteLine($"超时次数:{_expiredCount}");
            Console.WriteLine($"结果数量:{await db.Results.CountAsync()}");
        }
    }

    public class Source
    {
        public int Id1 { get; set; }

        public int Id2 { get; set; }
    }

    public class Result
    {
        public int Id1 { get; set; }

        public int Id2 { get; set; }
    }

    public class IdPair : IEquatable<IdPair>
    {
        public int Id1 { get; set; }

        public int Id2 { get; set; }

        public bool Equals(IdPair? other)
        {
            if(other is null)
                return false;
            return Id1 == other.Id1 && Id2 == other.Id2;
        }

        public override string ToString()
        {
            return $"{Id1},{Id2}";
        }
    }

    public class DemoDbContext : DbContext
    {
        public DbSet<Source> Sources { get; set; }

        public DbSet<Result> Results { get; set; }

        protected DbSet<QueryModelClass> QueryData { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("data source=test.db");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Source>().HasKey(it => new { it.Id1, it.Id2 });
            modelBuilder.Entity<Result>().HasKey(it => new { it.Id1, it.Id2 });
        }
    }
}
